/**
 * 
 */
package com.ej.generics;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Favor Generic methods
 * 
 * @author sunaina.sharma
 * 
 */
public class FavorGenMethods {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		// The compiler sees that both arguments to add are of type Set<String>,
		// so it knows that the
		// type parameter E must be String. This process is called type
		// inference.
		Set<String> guys = new HashSet<String>(Arrays.asList("Tom", "Dick",
				"Harry"));
		Set<String> stooges = new HashSet<String>(Arrays.asList("Larry", "Moe",
				"Curly"));
		Set<String> aflCio = add(guys, stooges);
		System.out.println(aflCio);

		// The type parameters appear redundantly on the left- and right-hand
		// sides of variable declarations.To eliminate this redundancy,
		// write a generic static factory method corresponding
		// to each constructor that you want to use.
		Map<String, List<String>> anagrams = newHashMap();
		System.out.println(anagrams.size());

		// Calling Generic singleton factory
		String[] strings = { "jute", "hemp", "nylon" };
		UnaryFunction<String> sameString = identityFunction();
		for (String s : strings)
			System.out.println(sameString.apply(s));
		Number[] numbers = { 1, 2.0, 3L };
		UnaryFunction<Number> sameNumber = identityFunction();
		for (Number n : numbers)
			System.out.println(sameNumber.apply(n));

		// A type parameter to be bounded by
		// some expression involving that type parameter itself is known as
		// recursive type bound
		List<String> names = new ArrayList<String>();
		names.add("Sunaina");
		names.add("Deepak");
		System.out.println("Maximum Sring length :: " + max(names));
	}

	/**
	 * 1. Using type parameters
	 * 
	 * @param <E>
	 * @param s1
	 * @param s2
	 * @return
	 */
	public static <E> Set<E> add(Set<E> s1, Set<E> s2) {
		Set<E> s3 = new HashSet<E>(s1);
		s3.addAll(s2);
		return s3;
	}

	/**
	 * 2. Generic static factory method
	 * 
	 * @param <K>
	 * @param <V>
	 * @return
	 */
	public static <K, V> HashMap<K, V> newHashMap() {
		return new HashMap<K, V>();
	}

	/**
	 * 3. Generic singleton Factory
	 * 
	 * @author Sunaina.Sharma
	 * @param <T>
	 */
	public interface UnaryFunction<T> {
		T apply(T arg);
	}

	private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
		public Object apply(Object arg) {
			return arg;
		}
	};

	@SuppressWarnings("unchecked")
	public static <T> UnaryFunction<T> identityFunction() {
		return (UnaryFunction<T>) IDENTITY_FUNCTION;
	}

	/**
	 * Recursive type bound
	 * 
	 * @param <T>
	 * @param list
	 * @return
	 */
	public static <T extends Comparable<T>> T max(List<T> list) {
		Iterator<T> i = list.iterator();
		T result = i.next();
		while (i.hasNext()) {
			T t = i.next();
			if (t.compareTo(result) > 0)
				result = t;
		}
		return result;
	}
}
